#include "Socket.h"
#include "InetAddress.h"
#include "Logger.h"

#include <unistd.h>
#include <sys/types.h> 
#include <sys/socket.h>
#include <strings.h>
#include <netinet/in.h>
#include <netinet/tcp.h>

Socket::~Socket()
{
    close(sockfd_);  // 关闭文件描述符，操作系统会释放与该文件描述符关联的资源
}

// 将套接字绑定到特定的本地地址和端口，指定套接字应该监听哪个网络接口和端口号
void Socket::bindAddress(const InetAddress &localaddr)
{
    if(0 != ::bind(sockfd_, (sockaddr*)localaddr.getSocAddr(), sizeof(sockaddr_in)))
    {
        LOG_FATAL("bind sockfd:%d fail.\n", sockfd_);
    }
}

void Socket::listen()
{
    if(0 != ::listen(sockfd_, 1024))  // 将一个已绑定的套接字转换为监听套接字，并设置监听队列的最大长度
    {
        LOG_FATAL("listen sockfd: %d fail.\n", sockfd_);
    }
}

int Socket::accept(InetAddress* peeraddr)
{
    sockaddr_in addr;  // 用于存储客户端的地址信息
    socklen_t len;  // 存储地址长度
    bzero(&addr, sizeof addr);

    /**
     * 调用系统调用accept()，等待客户端连接请求
     * sockfd_是之前已经绑定并监听的套接字描述符
     * accept()会阻塞直到有新的连接到达
     * 函数返回新创建的套接字描述符connfd，代表与客户端的连接
     * 同时，客户端的地址信息会被填充到addr中
     * 地址的实际长度会被写入len中
     */

    /**
     * 问题
     * 1. accept函数的参数不合法
     * 2. 对返回的connfd没有设置非阻塞
     * Reactor模型 one loop per thread
     * poller + non-blocking IO
     */ 
    // int connfd = ::accept(sockfd_, (sockaddr*)&addr, &len);
    int connfd = ::accept4(sockfd_, (sockaddr*)&addr, &len, SOCK_NONBLOCK | SOCK_CLOEXEC);
    if(connfd > 0)
    {
        peeraddr->setSockAddr(addr);  // 设置客户端的地址信息
    }
    return connfd;
}

void Socket::shutdownWrite()  // 关闭写端
{
    if(::shutdown(sockfd_, SHUT_WR) < 0)
    {
        LOG_ERROR("socket shutdownWrite error.\n");
    }
}

void Socket::setTcpNoDelay(bool on) // 直接发送，对数据不进行tcp缓冲；当启用时，数据会立即发送，而不是等待更多的数据以减少网络传输次数
{
    int optval = on ? 1 : 0;
    /**
     * setsockopt是一个系统调用，用于设置套接字选项
     * sockfd_是套接字描述符，表示要配置的套接字
     * IPPROTO_TCP表示要设置的是TCP协议的选项
     * TCP_NODELAY是具体的选项名称，用于控制Nagle算法，Nagle算法是一种旨在减少小数据包数量的算法，通过将多个小数据包合并成一个大数据包来发送，从而提高网络效率
     *  启用TCP_NODELAY（即optval为1）时，Nagle算法被禁用，数据会立即发送，不会等待更多的数据来合并
     *  禁用TCP_NODELAY（即optval为0）时，Nagle算法生效，数据可能会被合并后再发送，以减少网络传输次数，提高带宽利用率
     * &optval是指向选项值的指针，sizeof optval表示选项值的大小
     */
    ::setsockopt(sockfd_, IPPROTO_TCP, TCP_NODELAY, &optval, sizeof optval);
}

void Socket::setReuseAddr(bool on)  //
{
    int optval = on ? 1 : 0;
        /**
     * SO_REUSEADDR选项允许一个套接字绑定到一个已经被其他套接字使用的地址
     *  在服务器重启时，如果之前的套接字还没有完全关闭，新的套接字可以立即绑定到相同的地址和端口
     *  在多进程或多线程环境中，允许多个套接字绑定到同一个地址和端口，但每个套接字只能接收自己的数据
     * 启用SO_REUSEADDR后，可以避免“Address already in use”错误，但需要注意的是，这可能会导致数据混淆，特别是在多个套接字绑定到同一地址的情况下
     */
    ::setsockopt(sockfd_, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof optval);
}

void Socket::setReusePort(bool on)
{
    int optval = on ? 1 : 0;
    /**
     * SO_REUSEPORT选项允许多个套接字绑定到同一个端口，这对于负载均衡和多线程/多进程服务器非常有用
     * 当启用SO_REUSEPORT后，多个进程或线程可以同时绑定到同一个端口，操作系统会自动分发连接请求到不同的套接字上，从而实现负载均衡
     */
    ::setsockopt(sockfd_, SOL_SOCKET, SO_REUSEPORT, &optval, sizeof optval);
}

void Socket::setKeepAlive(bool on)
{
    int optval = on ? 1 : 0;
    /**
     * SO_KEEPALIVE选项用于启用TCP保持活动功能
     * 当启用时，TCP会定期发送探测包（keepalive probes）来检测连接是否仍然活跃
     * 如果连接的一方在一段时间内没有响应这些探测包，TCP会认为连接已经断开，并关闭连接
     */
    ::setsockopt(sockfd_, SOL_SOCKET, SO_KEEPALIVE, &optval, sizeof optval);
}